package com.cmc6.user.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.cmc6.common.config.BaseConfig;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.constant.BaseRegexConstant;
import com.cmc6.common.model.entity.BaseEntityThree;
import com.cmc6.common.model.entity.BaseEntityTwo;
import com.cmc6.common.model.entity.BaseUserSecurityDO;
import com.cmc6.common.model.enums.RequestCategoryEnum;
import com.cmc6.common.util.ApiResult;
import com.cmc6.common.util.MyJwtUtil;
import com.cmc6.common.util.ParamUtil;
import com.cmc6.common.util.RequestUtil;
import com.cmc6.common.util.otherplatforms.WxAppUtil;
import com.cmc6.user.exception.BizCodeEnum;
import com.cmc6.user.mapper.UserLoginMapper;
import com.cmc6.user.mapper.UserSecurityMapper;
import com.cmc6.user.model.dto.*;
import com.cmc6.user.model.entity.UserLoginDO;
import com.cmc6.user.model.entity.UserLoginWxDO;
import com.cmc6.user.model.entity.UserSecurityDO;
import com.cmc6.user.service.RegByWxService;
import com.cmc6.user.service.UserLoginService;
import com.cmc6.user.service.UserRegHelperService;
import com.cmc6.user.service.UserService;
import com.cmc6.user.util.PasswordConverterUtil;
import com.cmc6.websocket.model.entity.SocketDO;
import com.cmc6.websocket.service.SocketService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class UserLoginServiceImpl extends ServiceImpl<UserLoginMapper, UserLoginDO> implements UserLoginService {

    @Resource
    UserSecurityMapper userSecurityMapper;
    @Resource
    UserService userService;
    @Resource
    SocketService socketService;
    @Resource
    HttpServletRequest request;
    @Resource
    RedisTemplate<String, Long> redisTemplate;
    @Resource
    RedissonClient redissonClient;
    @Resource
    RegByWxService regByWxService;
    @Resource
    UserRegHelperService userRegHelperService;

    /**
     * 邮箱/手机号/用户自定义id 密码登录
     */
    @Override
    public String login(LoginDTO dto) {

        // 密码，非对称加密，解密
        dto.setPassword(userService.rsaDecrypt(dto.getPassword()));

        if (BaseConfig.adminUsername.equals(dto.getAccount()) && BaseConfig.adminEnable) {
            // 如果是 admin账户，并且配置文件中允许 admin登录
            return loginByAdmin(dto);
        }

        boolean isEmail = Pattern.matches(BaseRegexConstant.EMAIL, dto.getAccount());
        boolean isPhone = Pattern.matches(BaseRegexConstant.PHONE, dto.getAccount());
        if (isEmail || isPhone) {
            return loginByAccount(dto, isEmail, isPhone);
        }

        ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        return null; // 这里不会执行，只是为了语法检测
    }

    /**
     * 通过 admin登录
     */
    private String loginByAdmin(LoginDTO dto) {

        if (!BaseConfig.adminPassword.equals(dto.getPassword())) {
            handlerLoginErrorCount(BaseConstant.ADMIN_ID); // 账号密码错误次数过多时，直接锁定账号
            ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        }

        // admin jwt 一天过期
        return getLoginResult(BaseConstant.ADMIN_ID, false, null);
    }

    // 登录成功之后，返回给前端的数据
    private String getLoginResult(Long userId, boolean rememberMe, String jwtSecretSuf) {

        RequestCategoryEnum requestCategoryEnum = RequestUtil.getRequestCategoryEnum(request);

        // 获取互斥配置：1 所有都不互斥（默认） 2 相同端的互斥，H5/移动端/桌面程序 3 所有端都互斥
        String categorySetting = "1";
        String paramValue = ParamUtil.getValueById(BaseConstant.USER_MUTUALLY_EXCLUSIVE_ID);
        if (paramValue != null) {
            categorySetting = paramValue;
        }

        if ("1".equals(categorySetting)) {
            return MyJwtUtil.generateJwt(userId, rememberMe, jwtSecretSuf); // 颁发，并返回 jwt
        }

        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + BaseConstant.PRE_LOCK_LOGIN + userId);
        lock.lock();

        try {

            if ("2".equals(categorySetting)) {
                List<SocketDO> socketDbList =
                    socketService.lambdaQuery().eq(SocketDO::getUserId, userId).eq(BaseEntityThree::getEnableFlag, true)
                        .eq(SocketDO::getCategory, requestCategoryEnum.getCode()).select(BaseEntityTwo::getId).list();
                // 下线该类型的用户
                Set<Long> socketIdSet = socketDbList.stream().map(BaseEntityTwo::getId).collect(Collectors.toSet());
                socketService.offlineAndNoticeBySocketIdSetAndUserId(socketIdSet, userId, requestCategoryEnum);
            } else if ("3".equals(categorySetting)) {
                socketService.offlineAndNoticeByUserIdSet(Collections.singleton(userId)); // 下线其他
            }

            /**
             * 备注：如果这里修改了返回值，那么 {@link com.cmc6.request.aop.RequestAop#around} 也要同步进行修改
             */
            // 颁发，并返回 jwt
            return MyJwtUtil.generateJwt(userId, rememberMe, jwtSecretSuf);

        } finally {
            lock.unlock();
        }

    }

    /**
     * 通过账号登录
     */
    private String loginByAccount(LoginDTO dto, boolean isEmail, boolean isPhone) {

        UserLoginDO userLoginDO = null;

        if (isEmail) {
            userLoginDO = baseMapper.loginByEmailOrPhone(dto.getAccount(), null);
        } else if (isPhone) {
            userLoginDO = baseMapper.loginByEmailOrPhone(null, dto.getAccount());
        }

        // 账户是否存在
        if (userLoginDO == null) {
            ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        }

        // 判断是否：密码错误次数过多，已被冻结
        Boolean hasKey = redisTemplate.hasKey(BaseConstant.PRE_REDIS_LOGIN_ACCOUNT_BLACKLIST + userLoginDO.getUserId());
        if (BooleanUtil.isTrue(hasKey)) {
            ApiResult.error(BizCodeEnum.TOO_MANY_LOGIN_FAILURES);
        }

        if (StrUtil.isBlank(userLoginDO.getPassword())) {
            ApiResult.error(BizCodeEnum.NO_PASSWORD_SET); // 未设置密码，请点击忘记密码，进行密码设置
        }

        // 校验密码
        if (!PasswordConverterUtil.match(userLoginDO.getPassword(), dto.getPassword())) {
            handlerLoginErrorCount(userLoginDO.getUserId()); // 账号密码错误次数过多时，直接锁定账号
            ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        }

        // 校验成功之后，再判断是否被冻结，免得透露用户被封号的信息
        UserSecurityDO userSecurityDO = ChainWrappers.lambdaQueryChain(userSecurityMapper)
            .eq(BaseUserSecurityDO::getUserId, userLoginDO.getUserId())
            .select(BaseUserSecurityDO::getDelFlag, BaseUserSecurityDO::getEnableFlag,
                BaseUserSecurityDO::getJwtSecretSuf).one();

        if (userSecurityDO == null) {
            ApiResult.error(BizCodeEnum.LOSS_OF_ACCOUNT_INTEGRITY);
        }

        // 如果被注销了
        if (userSecurityDO.getDelFlag()) {
            handlerLoginErrorCount(userLoginDO.getUserId()); // 账号密码错误次数过多时，直接锁定账号
            ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        }

        if (!userSecurityDO.getEnableFlag()) {
            ApiResult.error(BizCodeEnum.ACCOUNT_IS_DISABLED);
        }

        return getLoginResult(userLoginDO.getUserId(), dto.isRememberMe(), userSecurityDO.getJwtSecretSuf());
    }

    /**
     * 账号密码错误次数过多时，直接锁定账号
     */
    private void handlerLoginErrorCount(Long userId) {

        ValueOperations<String, Long> ops = redisTemplate.opsForValue();

        String key = BaseConstant.PRE_REDIS_LOGIN_ERROR_COUNT + userId;

        Long redisTotal = ops.increment(key); // 次数 加 1

        if (redisTotal != null) {
            if (redisTotal == 1) {
                redisTemplate
                    .expire(key, BaseConstant.DAY_30_EXPIRE_TIME, TimeUnit.MILLISECONDS); // 等于 1表示，是第一次访问，则设置过期时间
            }
            if (redisTotal > 10) {
                // 超过十次密码错误，则封禁账号，下次再错误，则才会提示
                ops.set(BaseConstant.PRE_REDIS_LOGIN_ACCOUNT_BLACKLIST + userId, -1L);
                // 清空错误次数
                redisTemplate.delete(key);
            }
        }

    }

    /**
     * 微信 code 登录/注册
     */
    @Override
    @Transactional
    public String loginByWxCode(LoginByWxCodeDTO dto) {

        String openId = WxAppUtil.getOpenId(dto.getCode());
        if (StrUtil.isBlank(openId)) {
            ApiResult.error("操作失败：openId 为空");
        }

        RLock openIdLock =
            redissonClient.getLock(BaseConstant.PRE_REDISSON + BaseConstant.PRE_LOCK_WX_OPEN_ID + openId);
        openIdLock.lock();

        try {

            // 通过 微信openid，获取用户的一些信息
            LoginByOtherUserDTO loginByOtherUserDTO = baseMapper.loginByWxCodeGetUserByOpenId(openId);

            if (loginByOtherUserDTO == null) { // 如果 openId不存在用户，则根据手机号去获取 用户信息

                String phoneNumber = WxAppUtil.getPhoneNumber(dto.getPhoneCode());
                if (StrUtil.isBlank(phoneNumber)) {
                    ApiResult.error("操作失败：手机号为空");
                }

                RLock phoneLock = redissonClient
                    .getLock(BaseConstant.PRE_REDISSON + BaseConstant.PRE_LOCK_PHONE_REG_CODE + phoneNumber);
                phoneLock.lock();
                try {
                    // 通过手机号，获取用户的一些信息
                    loginByOtherUserDTO = baseMapper.loginByWxCodeGetUserByPhone(phoneNumber);

                    if (loginByOtherUserDTO == null) {
                        // 如果 openId和手机号都不存在，则进行注册
                        loginByOtherUserDTO =
                            regByWxService.regByWxOpenid(new UserLoginWxRegByWxOpenidDTO(openId, phoneNumber));
                    } else {
                        UserLoginWxDO userLoginWxDO = new UserLoginWxDO();
                        userLoginWxDO.setUserId(loginByOtherUserDTO.getUserId());
                        userLoginWxDO.setOpenid(openId);
                        regByWxService.save(userLoginWxDO); // 保存 openId
                    }
                } finally {
                    phoneLock.unlock();
                }
            }

            if (!loginByOtherUserDTO.isEnableFlag()) {
                ApiResult.error(BizCodeEnum.ACCOUNT_IS_DISABLED);
            }

            /**
             * 备注：如果这里修改了返回值，那么 {@link com.cmc6.request.aop.RequestAop#around} 也要同步进行修改
             */
            return MyJwtUtil.generateJwt(loginByOtherUserDTO.getUserId(), true, loginByOtherUserDTO.getJwtSecretSuf());
        } finally {
            openIdLock.unlock();
        }
    }

    /**
     * 手机号 登录/注册
     */
    @Override
    @Transactional
    public String loginByPhone(LoginByPhoneDTO dto) {

        String key = BaseConstant.PRE_LOCK_PHONE_REG_CODE + dto.getPhone();

        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + key);
        lock.lock();

        try {

            // 校验 code
            userRegHelperService.checkCodeByRedis(key, dto.getCode());

            // 通过手机号，获取用户的一些信息
            LoginByOtherUserDTO loginByOtherUserDTO = baseMapper.loginByWxCodeGetUserByPhone(dto.getPhone());

            if (loginByOtherUserDTO == null) { // 如果 手机号不存在，则进行注册
                UserLoginInsertOrUpdateDTO userLoginInsertOrUpdateDTO = new UserLoginInsertOrUpdateDTO();
                userLoginInsertOrUpdateDTO.setPassword("");
                userLoginInsertOrUpdateDTO.setPhone(dto.getPhone());
                userLoginInsertOrUpdateDTO.setEmail("");

                UserSecurityDO userSecurityDO = userRegHelperService.regBase(userLoginInsertOrUpdateDTO, false);

                loginByOtherUserDTO = new LoginByOtherUserDTO();
                loginByOtherUserDTO.setUserId(userSecurityDO.getUserId());
                loginByOtherUserDTO.setJwtSecretSuf(userSecurityDO.getJwtSecretSuf());
                loginByOtherUserDTO.setEnableFlag(true);
            }

            if (!loginByOtherUserDTO.isEnableFlag()) {
                ApiResult.error(BizCodeEnum.ACCOUNT_IS_DISABLED);
            }

            /**
             * 备注：如果这里修改了返回值，那么 {@link com.cmc6.request.aop.RequestAop#around} 也要同步进行修改
             */
            return MyJwtUtil.generateJwt(loginByOtherUserDTO.getUserId(), true, loginByOtherUserDTO.getJwtSecretSuf());

        } finally {
            lock.unlock();
        }
    }

    /**
     * 手机号 登录 发送验证码
     */
    @Override
    public String loginByPhoneSendCode(PhoneNotBlankDTO dto) {

        return userRegHelperService.phoneSendCode(dto.getPhone(), 2);
    }

}




